package com.bluexmicro.bluetooth


import android.annotation.SuppressLint
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothManager
import android.content.Context
import android.location.LocationManager
import android.os.Build
import android.os.Handler
import android.provider.Settings
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.util.*
import kotlin.coroutines.ContinuationInterceptor
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext

fun Context.isBluetoothEnable(): Boolean {
    val manager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager?
    return manager?.adapter?.isEnabled ?: false
}

fun Context.isLocationEnable(): Boolean {
    val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager?
    return locationManager?.isProviderEnabled(LocationManager.GPS_PROVIDER) ?: false
}

/**
 * @param autoConnect 自动连接
 * @param phy 大于Android 0有效
 * @see BluetoothDevice.PHY_LE_1M_MASK
 * @see BluetoothDevice.PHY_LE_2M_MASK
 * @see BluetoothDevice.PHY_LE_CODED_MASK
 * @param handler 大于Android 0有效，主要目的是让蓝牙在指定线程运行，如果是null则新建一个子线程运行
 *
 * 当 autoConnect = true 时 phy配置无效
 */
@SuppressLint("MissingPermission")
internal fun Context.connectBleDevice(
    device: BluetoothDevice,
    callback: BluetoothGattCallback,
    autoConnect: Boolean = false,
    phy: Int? = null,
    handler: Handler? = null
): BluetoothGatt? {
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && phy != null) {
        device.connectGatt(
            this,
            autoConnect,
            callback,
            BluetoothDevice.TRANSPORT_LE, phy, handler
        )
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        device.connectGatt(
            this,
            autoConnect,
            callback,
            BluetoothDevice.TRANSPORT_LE
        )
    } else {
        device.connectGatt(
            this,
            autoConnect,
            callback,
        )
    }
}

fun BluetoothGattCharacteristic.hasProperty(property: Int): Boolean {
    return this.properties and property > 0
}

fun Int.getIntBytes(size: Int = 4, order: ByteOrder = ByteOrder.LITTLE_ENDIAN): ByteArray {
    val buffer = ByteBuffer.allocate(4).order(order)
    buffer.putInt(this)
    val bytes = buffer.array()
    return if (order == ByteOrder.LITTLE_ENDIAN) {
        bytes.copyOfRange(0, size)
    } else {
        bytes.copyOfRange(4 - size, 4)
    }
}

/*
    //java
    /**
     * Convert a signed byte to an unsigned int.
     */
    private int unsignedByteToInt(byte b) {
        return b & 0xFF;
    }

    /**
     * Convert signed bytes to a 16-bit unsigned int.
     */
    private int unsignedBytesToInt(byte b0, byte b1) {
        return (unsignedByteToInt(b0) + (unsignedByteToInt(b1) << 8));
    }

    /**
     * Convert signed bytes to a 32-bit unsigned int.
     */
    private int unsignedBytesToInt(byte b0, byte b1, byte b2, byte b3) {
        return (unsignedByteToInt(b0) + (unsignedByteToInt(b1) << 8))
                + (unsignedByteToInt(b2) << 16) + (unsignedByteToInt(b3) << 24);
    }

*/

fun Byte.toUInt8(): Int {
    return this.toInt().and(0xff)
}

//小端序
fun toUInt16(b0: Byte, b1: Byte): Int {
    return (b0.toUInt8()).or(b1.toUInt8() shl 8)
}

//小端序
fun toUInt32(b0: Byte, b1: Byte, b2: Byte, b3: Byte): Int {
    return (b0.toUInt8()).or(b1.toUInt8() shl 8).or(b2.toUInt8() shl 16).or(b3.toUInt8() shl 24)
}

fun ByteArray.toHexString(char: Char? = null): String {
    if (isEmpty()) return "Empty Byte Array"
    val sb = StringBuilder()
    for (i in indices) {
        val tmp = this[i].toUInt8()
        sb.append(String.format("%02X", tmp))
        if (char != null) {
            sb.append(char)
        }
    }
    return sb.toString()
}

fun String.toUUID(): UUID {
    return UUID.fromString(this)
}

fun Int.intToBytes(): ByteArray {
    val b = ByteBuffer.allocate(4)
    b.putInt(this)
    return b.array()
}

//"aa:bb:cc:dd:ee:ff"->bytes=new byte[]{0xaa,0xbb,0xcc,0xdd,0xee,0xff}
fun String.macToByteArray(): ByteArray {
    return try {
        val splitResult = this.split(":")
        val temp = ArrayList<Byte>()
        for (s in splitResult) {
            temp.add(s.toInt(16).toByte())
        }
        temp.toByteArray()
    } catch (e: Exception) {
        byteArrayOf()
    }
}

fun ByteArray.merge(bytes: ByteArray): ByteArray {
    val newArray = ByteArray(this.size + bytes.size)
    System.arraycopy(this, 0, newArray, 0, size)
    System.arraycopy(bytes, 0, newArray, size, bytes.size)
    return newArray
}

fun Byte.merge(bytes: ByteArray): ByteArray {
    val newArray = ByteArray(1 + bytes.size)
    newArray[0] = this
    System.arraycopy(bytes, 0, newArray, 1, bytes.size)
    return newArray
}

fun ByteArray.merge(byte: Byte): ByteArray {
    val newArray = ByteArray(this.size + 1)
    System.arraycopy(this, 0, newArray, 0, size)
    newArray[newArray.lastIndex] = byte
    return newArray
}

fun Byte.merge(byte: Byte): ByteArray {
    val newArray = ByteArray(2)
    newArray[0] = this
    newArray[1] = byte
    return newArray
}


/**
 * Runs [fa], [fb] in parallel on [Dispatchers.Default] and combines their results using the provided function.
 *
 * ```kotlin
 * import arrow.fx.coroutines.*
 *
 * suspend fun main(): Unit {
 *   //sampleStart
 *   val result = parZip(
 *     { "First one is on ${Thread.currentThread().name}" },
 *     { "Second one is on ${Thread.currentThread().name}" }
 *   ) { a, b ->
 *       "$a\n$b"
 *     }
 *   //sampleEnd
 *  println(result)
 * }
 * ```
 * <!--- KNIT example-parzip-01.kt -->
 *
 * @param fa value to parallel zip
 * @param fb value to parallel zip
 * @param f function to zip/combine value [A] and [B]
 *
 * @see parZip for a function that can run on any [CoroutineContext]
 */
public suspend inline fun <A, B, C> parZip(
    crossinline fa: suspend CoroutineScope.() -> A,
    crossinline fb: suspend CoroutineScope.() -> B,
    crossinline f: suspend CoroutineScope.(A, B) -> C
): C = parZip(Dispatchers.Default, fa, fb, f)

/**
 * Runs [fa], [fb] in parallel on [ctx] and combines their results using the provided function.
 *
 * Coroutine context is inherited from a [CoroutineScope], additional context elements can be specified with [ctx] argument.
 * If the combined context does not have any dispatcher nor any other [ContinuationInterceptor], then [Dispatchers.Default] is used.
 * **WARNING** If the combined context has a single threaded [ContinuationInterceptor], this function will not run [fa], [fb] in parallel.
 *
 * ```kotlin
 * import arrow.fx.coroutines.*
 * import kotlinx.coroutines.Dispatchers
 *
 * suspend fun main(): Unit {
 *   //sampleStart
 *   val result = parZip(
 *     Dispatchers.IO,
 *     { "First one is on ${Thread.currentThread().name}" },
 *     { "Second one is on ${Thread.currentThread().name}" }
 *   ) { a, b ->
 *       "$a\n$b"
 *     }
 *   //sampleEnd
 *  println(result)
 * }
 * ```
 * <!--- KNIT example-parzip-02.kt -->
 *
 * @param fa value to parallel zip
 * @param fb value to parallel zip
 * @param f function to zip/combine value [A] and [B]
 *
 * @see parZip for a function that ensures operations run in parallel on the [Dispatchers.Default].
 */
public suspend inline fun <A, B, C> parZip(
    ctx: CoroutineContext = EmptyCoroutineContext,
    crossinline fa: suspend CoroutineScope.() -> A,
    crossinline fb: suspend CoroutineScope.() -> B,
    crossinline f: suspend CoroutineScope.(A, B) -> C
): C = coroutineScope {
    val faa = async(ctx) { fa() }
    val fbb = async(ctx) { fb() }
    val (a, b) = awaitAll(faa, fbb)
    f(a as A, b as B)
}